﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using ESRI.ArcGIS.Client.Symbols;
using ESRI.ArcGIS.Client.Tasks;
using ESRI.ArcGIS.Client.Toolkit;

using Microsoft.Surface.Presentation.Controls;

namespace eGrid_App
{
    /// <summary>
    /// This class is the container which holds the ArcGIS map.
    /// It defines the contact events needed to scale and pan the map. 
    /// It reflects these changes back onto the contained map.    
    /// </summary>
    public partial class ArcGisMap : UserControl
    {

        # region Instance Variables
        private Draw MyDrawObject;
        private Symbol _activeSymbol = null;
        private ScatterLayer parentLayer = null;
        private GhostFrame linkedGhostFrame = null;

        public ScatterLayer ParentScatterLayer
        {
            get
            {
                return parentLayer;
            }
            set
            {
                parentLayer = value;
            }
        }

        private MapLayersManager.MapLayerAddedDelegate addDelegate;
        private MapLayersManager.MapLayerDeletedDelegate deleteDelegate;

        private GraphicsLayer vehiclesLayer;
        private TroubleTicketGraphicLayer ticketsLayer;

        public bool isMainMap;

        # endregion

        # region Map Properties

        /// <summary>
        /// Width of the map
        /// </summary>
        public double MapWidth
        {
            get
            {
                return MyMap.Width;
            }

            set
            {
                MyMap.Width = value;
            }
        }

        /// <summary>
        /// Height of the map
        /// </summary>
        public double MapHeight
        {
            get
            {
                return MyMap.Height;
            }

            set
            {
                MyMap.Height = value;
            }
        }

        /// <summary>
        /// The initial extent of the map 
        /// </summary>
        private Envelope initialExtent;
        public Envelope InitialExtent
        {
            get
            {
                return initialExtent;
            }

            set
            {
                initialExtent = value;
            }
        }

        /// <summary>
        /// Wether the layers list should be visible on the map
        /// </summary>
        private bool showLayersList = false;
        public bool ShowLayersList
        {
            get
            {
                return showLayersList;
            }

            set
            {
                showLayersList = value;
                if (showLayersList)
                {
                    LayersListBorder.Visibility = Visibility.Visible;
                    LayersList.Visibility = Visibility.Visible;
                }
                else
                {
                    LayersListBorder.Visibility = Visibility.Hidden;
                    LayersList.Visibility = Visibility.Hidden;
                }
            }
        }

        /// <summary>
        /// Returns the center of the extent of the map
        /// </summary>
        public Point Center
        {
            get
            {
                MapPoint centerPoint = MyMap.Extent.GetCenter();
                return MyMap.MapToScreen(centerPoint);
            }
        }

        /// <summary>
        /// Returns the actual Map object 
        /// contained in this class.
        /// </summary>
        /// <returns>The actual Map object represented by this class</returns>
        public Map Map
        {
            get
            {
                return MyMap;
            }
            set
            {
                MyMap = value;
            }
        }

        /// <summary>
        /// For Testing: Return the URL of the Map
        /// </summary>
        public string MapURL
        {
            get
            {
                ArcGISTiledMapServiceLayer arcgisLayer = MyMap.Layers["StreetMap"] as ArcGISTiledMapServiceLayer;
                return arcgisLayer.Url;
            }
        }

        /// <summary>
        /// For Testing: Return the extent of the map
        /// </summary>
        public Envelope Extent
        {
            get
            {
                return MyMap.Extent;
            }
        }

        /// <summary>
        /// For Testing: Return the zoom factor of the map
        /// </summary>
        public double ZoomFactor
        {
            get
            {
                return MyMap.ZoomFactor;
            }
        }

        private bool canPan = true;
        public bool CanPan
        {
            get
            {
                return canPan;
            }

            set
            {
                canPan = value;
            }
        }

        private bool canZoom = true;
        public bool CanZoom
        {
            get
            {
                return canZoom;
            }

            set
            {
                canZoom = value;
            }
        }
        
        # endregion

        # region Constructor and Initializations
        
        /// <summary>
        /// Default constructor
        /// </summary>
        public ArcGisMap()
        {
            InitializeComponent();

            initialExtent = MyMap.Extent;

            foreach (Layer layer in MyMap.Layers)
            {
                if (layer.ID == "VehiclesLayer")
                {
                    vehiclesLayer = (GraphicsLayer)layer;
                }
                else if (layer.ID == "TroubleTicketsLayer")
                {
                    ticketsLayer = (TroubleTicketGraphicLayer)layer;
                }
            }

            //MyMap.GotTouchCapture += new EventHandler<TouchEventArgs>(MyMap_GotTouchCapture);
            //MyMap.LostTouchCapture += new EventHandler<TouchEventArgs>(MyMap_LostTouchCapture);

            initializeGraphics();

            isMainMap = false;

        }





        void MyMap_LostTouchCapture(object sender, TouchEventArgs e)
        {
            throw new NotImplementedException();
        }



        public void initializeCloneDelegates()
        {
            addDelegate = new MapLayersManager.MapLayerAddedDelegate(AddMapLayer);
            deleteDelegate = new MapLayersManager.MapLayerDeletedDelegate(RemoveMapLayer);

            MapLayersManager.Instance.MapLayerAdded += addDelegate;
            MapLayersManager.Instance.MapLayerDeleted += deleteDelegate;
        }




        /// <summary>
        /// This method is to be used only when this object is used as the background map in a scatter layer
        /// </summary>
        /// <param name="sl"></param>
        public void setParentScatterLayer(ScatterLayer sl)
        {
            parentLayer = sl;
        }

        /// <summary>
        /// This method is used to link the map with its ghost frame
        /// </summary>
        /// <param name="gf"></param>
        public void setLinkedGhostFrame(GhostFrame gf)
        {
            linkedGhostFrame = gf;
        }

        #endregion

        # region Map Event Handlers

        /// <summary>
        /// Scaling the map
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {

            if (e.PreviousSize.Height == 0)
                return;

            // Resize the map grid
            MapWidth = e.NewSize.Width;
            MapHeight = e.NewSize.Height;

            ReloadMap();
        }

        private void MyMap_Initialized(object sender, EventArgs e)
        {
            MyMap.ZoomTo(initialExtent);
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }

        private void MyMap_ExtentChanged(object sender, ExtentEventArgs e)
        {
            if (e.OldExtent == null)
            {
                initialExtent = e.NewExtent;
            }
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }
        
        public void MyMap_ExtentChanging(object sender, ESRI.ArcGIS.Client.ExtentEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.Extent = initialExtent;                
            }
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }

        private void MyMap_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
                e.Handled = true;
        }

        public void MyMap_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.ExtentChanging += new EventHandler<ESRI.ArcGIS.Client.ExtentEventArgs>(MyMap_ExtentChanging);
                initialExtent = MyMap.Extent;
                e.Handled = true;
            }            
        }

        public void MyMap_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.ExtentChanging -= MyMap_ExtentChanging;
                e.Handled = true;
            }
        }

        public void MyMap_TouchDown(object sender, TouchEventArgs e)
        {
            if (isMainMap)
                MyMap.IsManipulationEnabled = false;
            else
            {
                Map captured = sender as Map;

                captured.IsManipulationEnabled = true;

                if (!canPan || !canZoom)
                {
                    //MyMap.ExtentChanging += new EventHandler<ESRI.ArcGIS.Client.ExtentEventArgs>(MyMap_ExtentChanging);
                    //initialExtent = MyMap.Extent;

                    //e.TouchDevice.Capture(this);

                    //e.Handled = true;
                }
                else
                {
                    e.TouchDevice.Capture(captured);
                }
            }



            
        }

        public void MyMap_TouchUp(object sender, TouchEventArgs e)
        {
            if (!isMainMap)
            {
                Map captured = sender as Map;
                captured.IsManipulationEnabled = false;

                //BUG: with this condition. TouchDevices are allowed to be captured by multiple uielements in WPF, the Map does not always release the device
                //the bug is rescued by setting map.IsManipulationEnabled = false, stopping the map from moving even though it still captures touches
                //there should be a better way to do this ... 
                if (e.TouchDevice.Captured == captured)
                {
                    captured.ReleaseAllTouchCaptures();
                    captured.ReleaseMouseCapture();
                }
            }
            
        }
        

        # endregion

        # region ZoomingAndPanning
        
        /// <summary>
        /// Zoom in or out
        /// </summary>
        /// <param name="factor"></param>
        public void Zoom(double factor)
        {
            // > 1 = zoom in
            // < 1 = zoom out
            MyMap.Zoom(factor);
        }

        /// <summary>
        /// Zoom to a specific extent...
        /// Map animation will operate during the extent change. 
        /// </summary>
        /// <param name="geom"></param>
        public void ZoomTo(ESRI.ArcGIS.Client.Geometry.Geometry geom)
        {
            MyMap.ZoomTo(geom);
        }


        /// <summary>
        /// Pan to a screen point
        /// PanTo: Pan to an extent that centers on the ESRI.ArcGIS.Client.Geometry instance passed to the method. 
        /// Map scale will not change. Map animation will operate during the operation.
        /// </summary>
        /// <param name="screenPoint"></param>
        public void PanTo(System.Windows.Point screenPoint)
        {
            if (MyMap.Extent != null)
            {
                MapPoint mapPoint = MyMap.ScreenToMap(screenPoint);                                
                MyMap.PanTo(mapPoint);
            }

        }

        /// <summary>
        /// Set the extent from latitude and longitude.
        /// X values are longitudes and Y values are latitudes
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        public void setExtentFromLtdLng(double lat, double lng)
        {            
            double x1, y1, x2, y2;

            x1 = lng + 0.01;
            x2 = lng - 0.01;

            y1 = lat + 0.001;
            y2 = lat - 0.001;

            Envelope newExtent = new Envelope(x1, y1, x2, y2);
            MyMap.Extent = newExtent;            
        }

        /// <summary>
        /// Set the extent of the the map using an
        /// Evelope arugment such that the map will
        /// display the given area.
        /// </summary>
        /// <param name="input">The Envelope area to be shown</param>
        public void setExtentFromEnvelop(Envelope input)
        {
            if (MyMap.Extent == null)
            {
                Envelope env = new Envelope(input.XMax, input.YMax, input.XMin, input.YMin);
                MyMap.Extent = env;
            }
            else
            {
                MyMap.Extent = input;
            }

        }

        # endregion ZoomingAndPanning

        # region Annotations

        ///// <summary>
        ///// Used the change the visibility of the annotations toolbar
        ///// </summary>
        //public void ToggleAnnotationsToolbarVisibility()
        //{
        //    if (AnnotationsToolbar.Visibility == Visibility.Hidden)
        //        AnnotationsToolbar.Visibility = Visibility.Visible;
        //    else
        //        AnnotationsToolbar.Visibility = Visibility.Hidden;
        //}

        /// <summary>
        /// Initialize the graphics layer and draw object
        /// </summary>
        private void initializeGraphics()
        {
            MyDrawObject = new Draw(MyMap)
            {
                LineSymbol = LayoutRoot.Resources["DrawLineSymbol"] as LineSymbol,
                FillSymbol = LayoutRoot.Resources["DrawFillSymbol"] as FillSymbol
            };

            MyDrawObject.DrawComplete += MyDrawObject_DrawComplete;
            MyDrawObject.DrawMode = DrawMode.None;
            MyDrawObject.IsEnabled = false;
            
        }

        /// <summary>
        /// Event handler for Draw Complete event of the draw object
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void MyDrawObject_DrawComplete(object sender, ESRI.ArcGIS.Client.DrawEventArgs args)
        {
            AnnotationMasterLayer graphicsLayer = MapLayersManager.Instance.annotationsLayer as AnnotationMasterLayer; 
            ESRI.ArcGIS.Client.Graphic graphic = new ESRI.ArcGIS.Client.Graphic()
            {
                Geometry = args.Geometry,
                Symbol = _activeSymbol,
            };
            graphicsLayer.AddGraphic(graphic);
        }

        /// <summary>
        /// Returns whether the map is in the draw mode
        /// </summary>
        /// <returns>Whether the draw mode is enabled or not</returns>
        public bool isMapInDrawMode()
        {
            if (MyDrawObject.DrawMode != DrawMode.None)
            {
                return true;
            }
            return false;
        }

        // Returns whether the layers list is visible
        public bool isLayersListVisible()
        {
            if (showLayersList) return true;

            return false;
        }

        /// <summary>
        /// Used to set the draw mode to point
        /// </summary>
        public void SetDrawPointMode()
        {
            MyDrawObject.DrawMode = DrawMode.Point;
            _activeSymbol = LayoutRoot.Resources["DefaultMarkerSymbol"] as Symbol;
            
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to polyline
        /// </summary>
        public void SetDrawPolylineMode()
        {
            MyDrawObject.DrawMode = DrawMode.Polyline;
            _activeSymbol = LayoutRoot.Resources["DefaultLineSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to polygon
        /// </summary>
        public void SetDrawPolygonMode()
        {
            MyDrawObject.DrawMode = DrawMode.Polygon;
            _activeSymbol = LayoutRoot.Resources["DefaultFillSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to rectangle
        /// </summary>
        public void SetDrawRectangleMode()
        {
            MyDrawObject.DrawMode = DrawMode.Rectangle;
            _activeSymbol = LayoutRoot.Resources["DefaultFillSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to free hand
        /// </summary>
        public void SetDrawFreehandMode()
        {
            MyDrawObject.DrawMode = DrawMode.Freehand;
            _activeSymbol = LayoutRoot.Resources["DefaultLineSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Clears the graphics that are drawn on the map
        /// </summary>
        public void ClearGraphics()
        {
            StopDrawing();
            AnnotationMasterLayer graphicsLayer = MapLayersManager.Instance.annotationsLayer as AnnotationMasterLayer;
            graphicsLayer.ClearChildGraphics();
        }

        /// <summary>
        /// disabled the drawing mode
        /// </summary>
        public void StopDrawing()
        {
            MyDrawObject.DrawMode = DrawMode.None;
            MyDrawObject.IsEnabled = false;
            MyMap.IsManipulationEnabled = true;
        }        

        # endregion

        # region OtherMembers

 

        /// <summary>
        /// Refresh the map
        /// </summary>
        public void ReloadMap()
        {
            MyMap.UpdateLayout();
        }

        /// <summary>
        /// gets image of the current view
        /// </summary>
        /// <returns></returns>
        public ImageSource ToImageSource()
        {
            FrameworkElement obj = this;

            // Save current canvas transform
            Transform transform = obj.LayoutTransform;
            obj.LayoutTransform = null;

            // fix margin offset as well
            Thickness margin = obj.Margin;
            obj.Margin = new Thickness(0, 0,
            margin.Right - margin.Left, margin.Bottom - margin.Top);

            // Get the size of canvas
            System.Windows.Size size = new System.Windows.Size(obj.ActualWidth, obj.ActualHeight);

            // force control to Update
            obj.Measure(size);
            obj.Arrange(new Rect(size));

            RenderTargetBitmap bmp = new RenderTargetBitmap(
            (int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);

            bmp.Render(obj);

            // return values as they were before
            obj.LayoutTransform = transform;
            obj.Margin = margin;

            return bmp;
        }


        # endregion OtherMembers


        #region Delegated methods

        public void AddMapLayer(String addLayerID)
        {
            Layer addLayer = MapLayersManager.Instance.Clonelayer(addLayerID);
            MyMap.Layers.Add(addLayer);

            if (addLayer.ID == "VehiclesLayer")
            {
                vehiclesLayer = (GraphicsLayer)addLayer;
            }
            else if (addLayer.ID == "TroubleTicketsLayer")
            {
                ticketsLayer = (TroubleTicketGraphicLayer)addLayer;
            }
        }

        public void RemoveMapLayer(Layer deleteLayer)
        {
            LayerCollection layers = MyMap.Layers;

            foreach (Layer layer in layers)
            {
                if (layer.ID == deleteLayer.ID)
                {
                    MyMap.Layers.Remove(layer);
                    break;
                }
            }
        }





        #endregion

        ~ArcGisMap()
        {
            MapLayersManager.Instance.MapLayerAdded -= addDelegate;
            MapLayersManager.Instance.MapLayerDeleted -= deleteDelegate;
        }

    }
}
